﻿namespace Guia.Elements
{
    using System;
    using System.Diagnostics.Contracts;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Automation;
    using Core;
    using Locator;
    using Win32;

    /// <summary>
    /// Represents a particular UiElement
    /// </summary>
    public class UiElement : IUiElement
    {
        /// <summary>
        /// The AutomationElement representating this UiElement.
        /// </summary>
        protected AutomationElement automationElement;

        /// <summary>
        /// The TreeWaler is used to get the parent element of a specific UiElement.
        /// </summary>
        private readonly TreeWalker walker = TreeWalker.ControlViewWalker;

        /// <summary>
        /// The root UiElement.
        /// </summary>
        private static readonly UiElement rootElement = new UiElement(AutomationElement.RootElement);

        /// <summary>
        /// Initializes a new instance of the UiElement class. 
        /// </summary>
        /// <param name="automationElement">The AutomationElement representating this UiElement.</param>
        /// <exception cref="ArgumentNullException">Thrown when the given AutomationElement is null</exception>
        public UiElement(AutomationElement automationElement)
        {
            Contract.Requires<ArgumentNullException>(automationElement != null);
            this.automationElement = automationElement;
        }

        /// <summary>
        /// Gets the root UiElement.
        /// </summary>
        public static UiElement RootElement
        {
            get
            {
                return rootElement;
            }
        }

        /// <summary>
        /// Gets the underlying AutomationElement of this UiElement.
        /// </summary>
        public AutomationElement AutomationElement
        {
            get
            {
                return this.automationElement;
            }
        }

        /// <summary>
        /// Gets the tooltip text of this UiElement.
        /// If no tooltip is defined, an empty string is returned.
        /// </summary>
        public string ToolTip
        {
            get
            {
                return this.automationElement.Current.HelpText;
            }
        }

        /// <summary>
        /// Gets the parent UiElement of this UiElement.
        /// </summary>
        public UiElement ParentElement
        {
            get
            {
                AutomationElement node = this.automationElement;
                node = this.walker.GetParent(node);
                UiElement parent = new UiElement(node);
                return parent;
            }
        }

        /// <summary>
        /// Gets the ControlType of the UiElement.
        /// </summary>
        public ControlType ControlType
        {
            get
            {
                return this.automationElement.Current.ControlType;
            }
        }

        /// <summary>
        /// Gets the name of this UiElement.
        /// </summary>
        public string Name
        {
            get
            {
                return this.automationElement.Current.Name;
            }
        }

        /// <summary>
        /// Gets the AutomationId of this UiElement.
        /// </summary>
        public string AutomationId
        {
            get
            {
                return this.automationElement.Current.AutomationId;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the current element is enabled or not.
        /// </summary>
        public bool IsEnabled
        {
            get
            {
                return this.automationElement.Current.IsEnabled;
            }
        }

        /// <summary>
        /// Gets a value indicating whether IsOffscreen is true or not.
        /// </summary>
        public bool IsOffscreen
        {
            get
            {
                return this.automationElement.Current.IsOffscreen;
            }
        }

        /// <summary>
        /// Gets the Window whicht contains this UiElement. 
        /// </summary>
        public IWindow ParentWindow
        {
            get
            {
                UiElement parentElement;
                do
                {
                    parentElement = this.ParentElement;
                    if (parentElement.AutomationElement == RootElement.AutomationElement)
                    {
                        throw new OperationNotSupportedException("There is no parent window");
                    }
                }
                while (parentElement.ControlType != ControlType.Window);
                IWindow parentWindow = new Window(parentElement.automationElement);
                return parentWindow;
            }
        }

        /// <summary>
        /// Gets all UiElement objects of the given type within this UiElement.
        /// </summary>
        /// <typeparam name="T">The desired type of the elements.</typeparam>
        /// <returns>All internal UiElements of the given type within this UiElement.</returns>
        public T[] GetAll<T>() where T : UiElement
        {
            return UiElementFactory.GetAllUiElements<T>(this);
        }

        /// <summary>
        /// Gets the UiElement object of the given type and the given automationId within this UiElement.
        /// </summary>
        /// <typeparam name="T">The desired type of the element.</typeparam>
        /// <param name="automationId">The desired automationId of the element.</param>
        /// <returns>The internal UiElement of the given type and the given automationId within this UiElement.</returns>
        public T Get<T>(string automationId) where T : UiElement
        {
            return UiElementFactory.GetUiElement<T>(this, SearchCondition.ByAutomationId(automationId));
        }

        /// <summary>
        /// Gets the UiElement object of the given type and the given name within this UiElement.
        /// </summary>
        /// <typeparam name="T">The desired type of the element.</typeparam>
        /// <param name="name">The desired name of the element.</param>
        /// <returns>The internal UiElement of the given type and the given name within this UiElement.</returns>
        public T GetByName<T>(string name) where T : UiElement
        {
            return UiElementFactory.GetUiElement<T>(this, SearchCondition.ByName(name));
        }

        /// <summary>
        /// Gets the UiElement object of the given type and the given SearchCondition within this UiElement.
        /// </summary>
        /// <typeparam name="T">The desired type of the element.</typeparam>
        /// <param name="condition">The desired SearchCondition for the element.</param>
        /// <returns>The internal UiElement of the given type and the given SearchCondition within this UiElement.</returns>
        public T Get<T>(SearchCondition condition) where T : UiElement
        {
            return UiElementFactory.GetUiElement<T>(this, condition);
        }

        /// <summary>
        /// Gets all the UiElements object of the given type and the given SearchCondition within this UiElement.
        /// </summary>
        /// <typeparam name="T">The desired type of the elements.</typeparam>
        /// <param name="condition">The desired SearchCondition for the elements.</param>
        /// <returns>The internal UiElement objects of the given type and the given SearchCondition within this UiElement.</returns>
        public T[] GetAll<T>(SearchCondition condition) where T : UiElement
        {
            return UiElementFactory.GetAllUiElements<T>(this, condition);
        }

        /// <summary>
        /// Gets the UiElement object with the given SearchCondition within this UiElement.
        /// </summary>
        /// <param name="condition">The desired SearchCondition for the element.</param>
        /// <returns>The internal UiElement object with the given SearchCondition.</returns>
        public UiElement Get(SearchCondition condition)
        {
            return UiElementFactory.GetUiElement(this, condition);
        }

        /// <summary>
        /// Gets the UiElement object with the given automationId within this UiElement.
        /// </summary>
        /// <param name="automationId">
        /// The desired automationId of the element.
        /// </param>
        /// <returns>
        /// The internal UiElement with the given automationId within this UiElement.
        /// </returns>
        public UiElement Get(string automationId)
        {
            return UiElementFactory.GetUiElement(this, SearchCondition.ByAutomationId(automationId));
        }

        /// <summary>
        /// Gets the UiElement object with the given name within this UiElement.
        /// </summary>
        /// <param name="name">
        /// The desired name of the element.
        /// </param>
        /// <returns>
        /// The internal UiElement with the given name within this UiElement.
        /// </returns>
        public UiElement GetByName(string name)
        {
            return UiElementFactory.GetUiElement(this, SearchCondition.ByName(name));
        }

        /// <summary>
        /// Gets all the UiElement objects with the given SearchCondition within this UiElement.
        /// </summary>
        /// <param name="condition">The desired SearchCondition for the elements.</param>
        /// <returns>The internal UiElement objects with the given SearchCondition.</returns>
        public UiElement[] GetAll(SearchCondition condition)
        {
            return UiElementFactory.GetAllUiElements(this, condition);
        }

        /// <summary>
        /// Gets the defined UI Automation-Pattern from this element.
        /// </summary>
        /// <typeparam name="T">The desired UI Automation-Pattern</typeparam>
        /// <returns>The desired UI Automation-Pattern.</returns>
        /// <exception cref="OperationNotSupportedException"/>
        public T GetPattern<T>() where T : BasePattern
        {
            try
            {
                FieldInfo fieldInfo = typeof(T).GetField("Pattern");
                AutomationPattern value = (AutomationPattern)fieldInfo.GetValue(null);
                T pattern = (T)this.automationElement.GetCurrentPattern(value);
                return pattern;
            }
            catch (Exception ex)
            {
                throw new OperationNotSupportedException(ex.Message);
            }
        }

        /// <summary>
        /// Performs a right-click on this UiElement.
        /// </summary>
        public void ClickRight()
        {
            try
            {
                this.SetFocus();
            }
            catch { }

            Point point = this.automationElement.GetClickablePoint();
            MouseHelper.DoMouseRightClick((int)point.X, (int)point.Y);
        }

        /// <summary>
        /// Performs a left-click on this UiElement.
        /// </summary>
        public void ClickLeft()
        {
            try
            {
                this.SetFocus();    
            } catch {}
            
            Point point = this.automationElement.GetClickablePoint();
            MouseHelper.DoMouseLeftClick((int) point.X, (int) point.Y);
        }

        /// <summary>
        /// Performs a double-click on this UiElement.
        /// </summary>
        public void DoubleClick()
        {
            try
            {
                this.SetFocus();
            }
            catch { }

            Point point = this.automationElement.GetClickablePoint();
            MouseHelper.DoMouseLeftClick((int)point.X, (int)point.Y);
            Delay.ForMilliseconds(10);
            MouseHelper.DoMouseLeftClick((int)point.X, (int)point.Y);
        }

        /// <summary>
        /// Sets the focus to this UiElement.
        /// </summary>
        public void SetFocus()
        {
            this.automationElement.SetFocus();
        }

        /// <summary>
        /// Returns whether the objects equals or not.
        /// </summary>
        /// <param name="obj">
        /// The other object to check with.
        /// </param>
        /// <returns>
        /// True, if the object are equal, fals if not.
        /// </returns>
        public override bool Equals(object obj)
        {
            if (obj == this)
            {
                return true;
            }

            if (obj == null)
            {
                return false;
            }

            if (this.GetType() != obj.GetType())
            {
                return false;
            }

            UiElement other = (UiElement)obj;
            if (other.automationElement == this.automationElement)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Returns the HashCode.
        /// </summary>
        /// <returns>
        /// The HashCode.
        /// </returns>
        public override int GetHashCode()
        {
            return this.automationElement != null ? this.automationElement.GetHashCode() : 0;
        }
    }
}
